package org.kardigen.lg.service.server;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Set;

import javax.jdo.FetchPlan;
import javax.jdo.PersistenceManager;
import javax.jdo.identity.StringIdentity;

import com.google.gwt.autobean.server.impl.TypeUtils;
import com.google.gwt.requestfactory.server.ServiceLayerDecorator;
import com.google.gwt.requestfactory.shared.BaseProxy;
import com.google.gwt.requestfactory.shared.RequestContext;

public class LgServiceLayer extends ServiceLayerDecorator {

	// @Override
	// public Method resolveRequestContextMethod(String requestContextClass,
	// String methodName) {
	// Class<?> searchIn = Class.class;
	// try {
	// searchIn = Class.forName(requestContextClass);
	// } catch (ClassNotFoundException e) {
	// }
	// for (Method method : searchIn.getMethods()) {
	// if (method.getName().equals(methodName)) {
	// return method;
	// }
	// }
	// return report("Could not locate %s method %s::%s",
	// RequestContext.class.getSimpleName(), requestContextClass,
	// methodName);
	// }


// @Override
	// public Class<? extends Locator<?, ?>> resolveLocator(Class<?> domainType)
	// {
	// return LgGenericLocator.class;
	// }

	// @Override
	// public boolean isLive(Object domainObject) {
	// return true; // FIXME
	// }

// @Override
	// public <T> T loadDomainObject(Class<T> clazz, Object domainId) {
	// T result = null;
	//
	// PersistenceManager pm = PMF.get().getPersistenceManager();
	// try {
	// Object object = pm.getObjectById(domainId);
	// result = clazz.cast(object);
	// } catch (Exception e) {
	// throw new RuntimeException(e);
	// } finally {
	// pm.close();
	// }
	//
	// return result;
	// }


	@Override
	public Class<? extends BaseProxy> resolveClass(String typeToken) {
		Class<?> found = forName(typeToken);
		// if (!EntityProxy.class.isAssignableFrom(found) &&
		// !ValueProxy.class.isAssignableFrom(found)) {
		// die(null, "The requested type %s is not assignable to %s or %s",
		// typeToken,
		// EntityProxy.class.getCanonicalName(),
		// ValueProxy.class.getCanonicalName());
		// }
		return found.asSubclass(BaseProxy.class);
	}

	/** {@inheritDoc} */
	@Override
	public <T> Class<? extends T> resolveClientType(Class<?> domainClass, Class<T> clientType, boolean required) {

		if (List.class.isAssignableFrom(domainClass)) {
			return List.class.asSubclass(clientType);
		}
		if (Set.class.isAssignableFrom(domainClass)) {
			return Set.class.asSubclass(clientType);
		}
		if (TypeUtils.isValueType(domainClass)) {
			return domainClass.asSubclass(clientType);
		}

		String name = clientType.getName();
		// TODO read type from client from annotation
		if (name != null) {
			return forName(name).asSubclass(clientType);
		}

		if (required) {
			die(null, "The domain type %s cannot be sent to the client", domainClass.getCanonicalName());
		}
		return null;
	}

	// /** {@inheritDoc} */
	// @Override
	// public Method resolveDomainMethod(Method requestContextMethod) {
	// Class<?> contextClass = requestContextMethod.getDeclaringClass();
	// Class<?> searchIn = serviceClassMap.get(contextClass);
	// if (searchIn == null) {
	// report("No service defined for method %s",
	// requestContextMethod.getName());
	// }
	//
	// Class<?>[] parameterTypes = requestContextMethod.getParameterTypes();
	// Class<?>[] domainArgs = new Class<?>[parameterTypes.length];
	// for (int i = 0, j = domainArgs.length; i < j; i++) {
	// if (BaseProxy.class.isAssignableFrom(parameterTypes[i])) {
	// domainArgs[i] =
	// getTop().resolveDomainClass(parameterTypes[i].asSubclass(BaseProxy.class));
	// } else if (EntityProxyId.class.isAssignableFrom(parameterTypes[i])) {
	// domainArgs[i] =
	// TypeUtils.ensureBaseType(TypeUtils.getSingleParameterization(EntityProxyId.class,
	// requestContextMethod.getGenericParameterTypes()[i]));
	// } else {
	// domainArgs[i] = parameterTypes[i];
	// }
	// }
	//
	// Method method = searchMethod(searchIn, requestContextMethod.getName(),
	// domainArgs);
	// if (method == null) {
	// report("Could not locate domain method %s",
	// requestContextMethod.getName());
	// }
	// return method;
	// // Throwable ex;
	// // try {
	// // return searchIn.getMethod(requestContextMethod.getName(), domainArgs);
	// // } catch (SecurityException e) {
	// // ex = e;
	// // } catch (NoSuchMethodException e) {
	// // return
	// // }
	// // return die(ex, "Could not get domain method %s in type %s",
	// // requestContextMethod.getName(), searchIn.getCanonicalName());
	// }

	// private Method searchMethod(Class searchIn, String methodName, Class<?>[]
	// domainArgs) {
	// Method[] methods = searchIn.getMethods();
	// for (Method method : methods) {
	// if (method.getName().equals(methodName)) {
	// if (matchMethodArguments(method, domainArgs)) {
	// return method;
	// }
	// }
	// }
	// return null;
	// }

	// private boolean matchMethodArguments(Method method, Class<?>[] domainArgs)
	// {
	// Class<?>[] parameterTypes = method.getParameterTypes();
	// if (parameterTypes.length != domainArgs.length) {
	// return false;
	// }
	// for (int i = 0; i < parameterTypes.length; i++) {
	// if (parameterTypes[i].isAssignableFrom(domainArgs[i])) {
	// return true;
	// }
	// }
	// return false;
	// }

	/** {@inheritDoc} */
	@Override
	public Method resolveRequestContextMethod(String requestContextClass, String methodName) {

		// TODO consider validation here

		Class<?> searchIn = forName(requestContextClass);
		for (Method method : searchIn.getMethods()) {
			if (method.getName().equals(methodName)) {
				return method;
			}
		}
		return report("Could not locate %s method %s::%s", RequestContext.class.getSimpleName(), requestContextClass,
		      methodName);
	}

	/** {@inheritDoc} */
	@Override
	public boolean isLive(Object domainObject) {
		// if (domainObject instanceof Entity) {
		// return (!requestProvider.get().isEntityRemoved((Entity) domainObject));
		// }
		// FIXME
		return true;
	}

	/** {@inheritDoc} */
	@Override
	public Class<?> getIdType(Class<?> domainType) {
		return String.class;
	}

	/** {@inheritDoc} */
	@Override
	public <T> T loadDomainObject(Class<T> clazz, Object domainId) {
		// TiaSoaLocator<?> locator = locatorMap.get(clazz);
		// Entity entity = locator.findEntity((String) domainId);
		// requestProvider.get().trackChanges(entity);
		// return clazz.cast(entity);
		if (domainId == null) {
			return null;
		}

		PersistenceManager pm = PMF.get().getPersistenceManager();
		try {
			pm.getFetchPlan().addGroup(FetchPlan.ALL);

			StringIdentity stringIdentity = new StringIdentity(clazz, (String) domainId);
			Object object = pm.getObjectById(stringIdentity);
			return clazz.cast(pm.detachCopy(object));
		} catch (Exception e) {
			throw new RuntimeException(e);
		} finally {
			pm.close();
		}
	}

	/** {@inheritDoc} */
	@Override
	public boolean requiresServiceLocator(Method contextMethod, Method domainMethod) {

		return false;
	}

	/** {@inheritDoc} */
	// @Override
	// public Object invoke(Method domainMethod, Object... args) {
	// // requestProvider.get().fillParentLists(); TODO remove this line
	// /*
	// * Security consideration - we will never get to this point, if service
	// * was not declared in serviceClassMap (see RequestBinder).
	// */
	// Object service = injector.getInstance(domainMethod.getDeclaringClass());
	// Throwable ex;
	// try {
	// return domainMethod.invoke(service, args);
	// } catch (IllegalArgumentException e) {
	// ex = e;
	// } catch (IllegalAccessException e) {
	// ex = e;
	// } catch (InvocationTargetException e) {
	// return report(e);
	// } finally {
	// requestProvider.get().dispose(); // if any
	// }
	// return die(ex, "Could not invoke method %s", domainMethod.getName());
	// }

	//
	// /** {@inheritDoc} */
	// @Override
	// public Object getProperty(Object domainObject, String property) {
	// if (domainObject instanceof TiaTablebasedOot) {
	// try {
	// return getTop().getProperty(domainObject, property);
	// } catch (UnexpectedException e) {
	// if (e.getCause() instanceof NoSuchMethodException) {
	// String attributeName = LOWER_CAMEL.to(UPPER_UNDERSCORE, property);
	// Attribute attribute =
	// pl.ncdc.tia.soa.entity.util.Attributes.findAttribute(((TiaTablebasedOot)
	// domainObject), attributeName);
	// if (attribute == null) {
	// die(e, message, args);
	// }
	// return
	// pl.ncdc.tia.soa.entity.util.Attributes.getAttributeValue(attribute);
	// } else {
	// throw e;
	// }
	// }
	// } else {
	// getTop().getProperty(domainObject, property);
	// }
	// }

	@Override
	public Object getVersion(Object domainObject) {
		try {
			return domainObject.getClass().getMethod("getVersion").invoke(domainObject);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	/** {@inheritDoc} */
	// @Override
	// public Object getVersion(Object domainObject) {
	// if (domainObject instanceof TiaTablebasedOot) {
	// VersionToken version = ((TiaTablebasedOot)
	// domainObject).getVersionToken();
	// if (version != null) {
	// return version.getRecordVersion();
	// } else {
	// return Long.valueOf(-1);
	// }
	// } else {
	// return Long.valueOf(-1);
	// }
	// }

	@Override
	public Object getId(Object domainObject) {
		if (domainObject == null) {
			return null;
		}

		PersistenceManager pm = PMF.get().getPersistenceManager();
		try {
			Method method = domainObject.getClass().getMethod("getId");
			return method.invoke(domainObject);
			// StringIdentity identity = (StringIdentity)
			// pm.getObjectId(domainObject);
			// if (identity == null) {
			// return null;
			// }
			//
			// return identity.getTargetClassName() + ":" + identity.getKey();

		} catch (Exception e) {
			throw new RuntimeException(e);
		} finally {
			pm.close();
		}
	}

	// /** {@inheritDoc} */
	// @Override
	// public Object getId(Object domainObject) {
	// if (domainObject instanceof Entity) {
	// Entity entity = (Entity) domainObject;
	// EntityStack stack = requestProvider.get().getEntityStack();
	// stack.push(entity);
	// String entityId = stack.getEntityId();
	// stack.pop();
	// return entityId;
	// } else {
	// return "-1";
	// }
	// }

	// /** {@inheritDoc} */
	// @Override
	// public void setProperty(Object domainObject, String property, Class<?>
	// expectedType, Object value) {
	//
	// if (List.class.isAssignableFrom(expectedType)) {
	// if (value != null) {
	// Object oldValue = getTop().getProperty(domainObject, property);
	// ChangeNotifyingList<?> list;
	// if (oldValue != null) {
	// list = (ChangeNotifyingList<?>) oldValue;
	// list.clear();
	// } else {
	// list = ChangeNotifyingList.wrap(new ArrayList());
	// super.setProperty(domainObject, property, expectedType, list);
	// }
	// list.addAll((List) value);
	// } else {
	// super.setProperty(domainObject, property, expectedType, value);
	// }
	// } else {
	// super.setProperty(domainObject, property, expectedType, value);
	// }
	// }

	/**
	 * Call {@link Class#forName(String)} and report any errors through
	 * {@link #die()}.
	 */
	private Class<?> forName(String name) {
		try {
			return Class.forName(name, false, Thread.currentThread().getContextClassLoader());
		} catch (ClassNotFoundException e) {
			return die(e, "Could not locate class %s", name);
		}
	}

	// public class MessageConstraintViolation<T> implements
	// ConstraintViolation<T> {
	//
	// private final String messageText;
	// private final Path path;
	//
	// public MessageConstraintViolation(String messageText, String path) {
	// this.messageText = messageText;
	// this.path = new MessagePath(path);
	// }
	//
	// @Override
	// public String getMessage() {
	// return messageText;
	// }
	//
	// @Override
	// public Path getPropertyPath() {
	// return path;
	// }
	//
	// @Override
	// public String getMessageTemplate() {
	// throw new UnsupportedOperationException();
	// }
	//
	// @Override
	// public T getRootBean() {
	// throw new UnsupportedOperationException();
	// }
	//
	// @Override
	// public Class<T> getRootBeanClass() {
	// throw new UnsupportedOperationException();
	// }
	//
	// @Override
	// public Object getLeafBean() {
	// throw new UnsupportedOperationException();
	// }
	//
	// @Override
	// public Object getInvalidValue() {
	// throw new UnsupportedOperationException();
	// }
	//
	// @Override
	// public ConstraintDescriptor<?> getConstraintDescriptor() {
	// throw new UnsupportedOperationException();
	// }
	// }
	//
	// public class MessagePath implements Path {
	//
	// private final String pathString;
	//
	// public MessagePath(String pathString) {
	// this.pathString = pathString;
	// }
	//
	// @Override
	// public String toString() {
	// return pathString;
	// }
	//
	// @Override
	// public Iterator<Node> iterator() {
	// throw new UnsupportedOperationException();
	// }
	// }

}
